/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.ui.wizards;

import java.awt.Color;
import java.awt.Component;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.plaf.basic.BasicTextUI;
import javax.swing.text.JTextComponent;

import org.apache.log4j.Logger;
import org.isqlviewer.swing.action.SwingEventManager;
import org.isqlviewer.ui.ThrowableView;

/**
 * Context to support custom wizards by providing common functionality.
 * <p>
 * This object is in the same spirit of a ServletContext, this class represents common functionality for all custom
 * wizards.
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public abstract class WizardContext {

    private Logger logger = Logger.getLogger(getClass());
    private Hashtable<String, Object> attributes = new Hashtable<String, Object>();
    private SwingEventManager eventManager;

    protected WizardContext(SwingEventManager eventManager) {

        this.eventManager = eventManager;
    }

    /**
     * Logs informational error and supplementary message.
     * <p>
     * if informational messages are enabled the message and exception will be logged to designated destination.
     * 
     * @param message informational message to log.
     * @param error exception associated with the message.
     */
    public void info(Object message, Throwable error) {

        if (logger.isInfoEnabled()) {
            logger.info(message, error);
        }
    }

    /**
     * Logs an informational message.
     * <p>
     * if informational loggins is enabled the message will be logged to designated destination.
     * 
     * @param message informational message to log.
     */
    public void info(Object message) {

        if (logger.isInfoEnabled()) {
            logger.info(message);
        }
    }

    /**
     * Logs debugging messages and supplementary exception.
     * <p>
     * if debug messages are enabled the message and exception will be logged to designated destination.
     * 
     * @param message debug message to log.
     * @param error exception associated with the message.
     */
    public void debug(Object message, Throwable error) {

        if (logger.isDebugEnabled()) {
            logger.debug(message, error);
        }
    }

    /**
     * Logs message for debugging purposes.
     * <p>
     * if debugging is enabled the message will be logged to designated destination.
     * 
     * @param message debug information to log.
     */
    public void debug(Object message) {

        if (logger.isDebugEnabled()) {
            logger.debug(message);
        }
    }

    /**
     * Logs error messages and supplementary exception.
     * <p>
     * if error messages are enabled the message and exception will be logged to designated destination.
     * 
     * @param message error message to log.
     * @param error exception associated with the message.
     */
    public void error(Object message, Throwable error) {

        logger.error(message, error);
    }

    /**
     * Logs an error message.
     * <p>
     * if error logging is enabled the message will be logged to designated destination.
     * 
     * @param message error message to log.
     */
    public void error(Object message) {

        logger.error(message);
    }

    /**
     * Logs warning messages and supplementary exception.
     * <p>
     * 
     * @param message warning message to log.
     * @param error exception associated with the message.
     */
    public void warn(Object message, Throwable error) {

        logger.warn(message, error);
    }

    /**
     * Logs warning messages.
     * <p>
     * 
     * @param message warning information to log.
     */
    public void warn(Object message) {

        logger.warn(message);
    }

    /**
     * Marks a text component as invalid to the user.
     * <p>
     * If a text field has invalid input than this method should be called to provide consistent feedback to the user
     * with the notion of invalid input.
     * 
     * @param textArea to mark as incorrect or invalid.
     * @see #unmarkTextComponentIncorrect(JTextComponent)
     */
    public void markTextComponentIncorrect(JTextComponent textArea) {

        textArea.setForeground(UIManager.getColor("ToolTip.foreground"));
        textArea.setBackground(UIManager.getColor("ToolTip.background"));
    }

    /**
     * Restores an invalid text component to its original state.
     * <p>
     * Use this method in conjunction with markTextComponentIncorrect() when the user input has become valid.
     * 
     * @param textArea to restore and mark as valid.
     * @see #markTextComponentIncorrect(JTextComponent)
     */
    public void unmarkTextComponentIncorrect(JTextComponent textArea) {

        String prefix = null;
        BasicTextUI uiClass = (BasicTextUI) textArea.getUI();
        // Why they didn't make the property prefix public in the UI is beyond me >.>
        if (uiClass instanceof BasicTextAreaUI) {
            prefix = "TextArea";
        } else if (uiClass instanceof BasicTextFieldUI) {
            prefix = "TextField";
        } else if (uiClass instanceof BasicEditorPaneUI) {
            prefix = "EditorPane";
        }
        if (prefix == null) {
            textArea.setForeground(Color.BLACK);
            textArea.setBackground(Color.WHITE);
        } else {
            textArea.setForeground(UIManager.getColor(prefix + ".foreground"));
            textArea.setBackground(UIManager.getColor(prefix + ".background"));
        }
    }

    /**
     * Sets an attribute in this context.
     * <p>
     * If an attribute already exists with the given name it will override it with the new value.
     * 
     * @param name of the object to bound to this context.
     * @param attribute the value being bound to this context by the given name.
     * @throws NullPointerException if name or attribute are null;
     */
    public void setAttribute(String name, Object attribute) {

        String normalized = name.toLowerCase().trim();
        attributes.put(normalized, attribute);
    }

    /**
     * Removes the selected attribute by name from this context.
     * <p>
     * Once the attribute has been removed it will no longer be accesible from this context.
     * 
     * @param name of the attribute to remove.
     * @throws NullPointerException if name is null.
     */
    public void removeAttribute(String name) {

        String normalized = name.toLowerCase().trim();
        attributes.remove(normalized);
    }

    /**
     * Provides an enumeration of all available attributes in the current context.
     * <p>
     * 
     * @return enumeration of all the attribute names currently bound in this context.
     */
    public Enumeration<String> getAttributeNames() {

        return attributes.keys();
    }

    /**
     * Returns the context attribute with the given name, or null if there is no attribute by that name.
     * <p>
     * An attribute allows a wizard runner instance to give the custom wizard additional information inbetween steps.
     * 
     * @return an Object containing the value of the attribute, or null if no attribute exists matching the given name
     * @throws NullPointerException if name is null
     */
    public Object getAttribute(String name) {

        String normalized = name.toLowerCase().trim();
        return attributes.get(normalized);
    }

    /**
     * Shows a generic error dialog to the user with an error and message.
     * <p>
     * This implementation allows wizard code to notify the user of an error without having to have a reference to an
     * actual visual component.
     * 
     * @param error with an accompanying stack-trace the display can be null.
     * @param message descriptive message as to the source of error.
     */
    public abstract void showErrorDialog(Throwable error, String message);

    /**
     * Updates the list of setps after the current step.
     * <p>
     * Often wizards will allow a user to take different action leading to different set of steps. this function allows
     * a wizard to change the steps following the current step. So that the user will see the new steps they will be
     * taking as part of thier choice in the wizard.
     * 
     * @param newSteps collection of steps to modify after the current step.
     */
    public abstract void updateSteps(Collection<Step> newSteps);

    /**
     * Shows a generic error dialog to the user with an error and message.
     * <p>
     * This implementation allows wizard code to notify the user of an error without having to have a reference to an
     * actual visual component.
     * 
     * @param error with an accompanying stack-trace the display can be null.
     * @param message descriptive message as to the source of error.
     * @param owner component owner to show the error dialog from.
     */
    public void showErrorDialog(Component owner, Throwable error, String message) {

        ThrowableView view = new ThrowableView(error, message);
        JPanel container = new JPanel();
        view.doLayout(container, null, null);
        view.initializeView();
        container.invalidate();
        try {
            error(message, error);
            JOptionPane.showMessageDialog(owner, container, null, JOptionPane.ERROR_MESSAGE);
        } finally {
            view.disposeView(null);
        }
    }

    /**
     * Shows a generic information dialog to the user with a message
     * <p>
     * This implementation allows wizard code to notify the user informational message without having to have a
     * reference to an actual visual component.
     * 
     * @param message descriptive message to the user.
     */
    public abstract void showInformationDialog(String message);

    /**
     * Allows pragmatic navigation through other events beyond clicking 'Next' in a wizard.
     * <p>
     */
    public abstract void invokeNext();

    /**
     * Shows a generic information dialog to the user with a message
     * <p>
     * 
     * @param message descriptive message to the user.
     * @param owner component owner to show the informational dialog from.
     */
    public void showInformationDialog(Component owner, String message) {

        String html = null;
        if (message != null && !message.startsWith("<html>")) {
            html = "<html>".concat(message.trim());
        } else if (message == null) {
            html = "";
        }
        JOptionPane.showMessageDialog(owner, html, null, JOptionPane.INFORMATION_MESSAGE);
    }

    public SwingEventManager getEventManager() {

        return eventManager;
    }

}
